﻿using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using NAudio.Wave.SampleProviders;
using iStudio.Structure.Project;
using iStudio.Structure.Wave;

using NAudio.Wave;
using WaveFormat = iStudio.Structure.Wave.WaveFormat;

namespace iStudio.Format.NAudioFormat
{
    [Export(typeof(Format))]
    public class NAudioFormat : Format
    {
        private const int BufferSize = 16384;

        public NAudioFormat ()
        {
            Name = "NAudioFormat";

            RegisterFormat (FormatType.Wav, FormatSupport.Complete);
            RegisterFormat (FormatType.Mp3, FormatSupport.CanOpen);
            RegisterFormat (FormatType.Aiff, FormatSupport.CanOpen);
        }

        protected override List<WaveTrack> ExecuteImport(string file)
        {
            return OpenStream (File.Open (file, FileMode.Open, FileAccess.Read));
        }

        protected override void ExecuteExport(string file, FormatType type, WaveProject project)
        {
            //
            // Mix tracks together and output them
            //

            WaveMixerStream32 waveMixer = new WaveMixerStream32();

            //
            // We are going to mix together every track in the list
            //

            foreach (WaveTrack track in project.TrackList)
            {
                //
                // Exception, cannot play two different formats together, so we will always output stereo -> fake stereo with outputing 2 mono channels, same to each speaker
                //

                WaveStream trackStream;

                if (track.WaveFormat.Channels < 2)
                {
                    //
                    // As only for playing we can create a stereo track fro
                    //

                    WaveTrack stereoTrack = new WaveTrack(track.WaveFormat)
                    {
                        WaveFormat =
                        {
                            Channels = 2
                        }
                    };

                    // Create a single chunk of track length
                    WaveChunk stereoChunk = new WaveChunk(2, track.Length * 2);

                    //
                    // Paste all chunks twice onto stereo chunk
                    //

                    foreach (WaveChunk chunk in track.TrackChunks)
                    {
                        for (int i = 0; i < chunk.Length; i++)
                        {
                            stereoChunk.WaveBuffer[(chunk.Offset + i) * 2] = chunk.WaveBuffer[i];
                            stereoChunk.WaveBuffer[(chunk.Offset + i) * 2 + 1] = chunk.WaveBuffer[i];
                        }
                    }

                    //
                    // Add stereo track to mixer
                    //

                    stereoTrack.TrackChunks.Add(stereoChunk);

                    //
                    // Allows streams to hook into each track
                    //

                    trackStream = new WaveFileReader(TrackToStream(stereoTrack));
                }
                else
                {
                    //
                    // Allows streams to hook into each track
                    //

                    trackStream = new WaveFileReader(TrackToStream(track));
                }

                waveMixer.AddInputStream(trackStream);
            }

            WaveFileWriter.CreateWaveFile (file, waveMixer);
        }

        protected override List<WaveTrack> OpenStream(Stream stream)
        {
            WaveStream reader = new AudioFileReader(stream);
            ISampleProvider sampleProvider = (ISampleProvider)reader;

            //
            // All streams will be opened with default 44100 Hz Sample rate
            //
            WaveFormat format = new WaveFormat
                                    {
                                        Channels = reader.WaveFormat.Channels,
                                        BitsPerSample = 32,
                                        SampleRate = 44100
                                    };

            //
            // Resample using NAudio
            //

            //if (reader.WaveFormat.SampleRate != 44100)
            //{
            //    //// NAudio resampler too buggy atm, use workaround
            //    ////
            //    //// Create resampling stream and sample stream
            //    ////

            //    //NAudio.Wave.WaveFormat resampled = NAudio.Wave.WaveFormat.CreateCustomFormat (WaveFormatEncoding.Pcm, 44100, reader.WaveFormat.Channels, reader.WaveFormat.AverageBytesPerSecond, reader.WaveFormat.BlockAlign, reader.WaveFormat.BitsPerSample);

            //    ////
            //    //// Resampler only supports PCM encoding, so convert to PCM format
            //    ////

            //    //if (reader.WaveFormat.Encoding != WaveFormatEncoding.Pcm)
            //    //{
            //    //    NAudio.Wave.WaveFormat converted = NAudio.Wave.WaveFormat.CreateCustomFormat(WaveFormatEncoding.Pcm, reader.WaveFormat.SampleRate, reader.WaveFormat.Channels, reader.WaveFormat.AverageBytesPerSecond, reader.WaveFormat.BlockAlign, reader.WaveFormat.BitsPerSample);

            //    //    reader = new WaveFormatConversionStream (converted, reader);
            //    //}

            //    //IWaveProvider resampleProvider = new ResamplerDmoStream(reader, resampled);
            //    //sampleProvider = new WaveToSampleProvider(resampleProvider);
            //}

            //
            // Read samples from sample provider until last block
            //

            float[] buffer = new float[BufferSize];

            List<float> samples = new List<float>();

            int readSamples = 0;
            int totalSamples = 0;

            while ( (readSamples = sampleProvider.Read(buffer, 0, BufferSize)) > 0)
            {
                totalSamples += readSamples;
                samples.AddRange(buffer);
            }

            //
            // Remove samples that buffer added if last block was shorter
            //

            if (totalSamples != samples.Count)
            {
                samples.RemoveRange(totalSamples, (samples.Count - totalSamples));
            }

            if (reader.WaveFormat.SampleRate != 44100)
            {
                //
                // TODO: Delete this part of code when NAudio is able to resample
                // Create new array with correct number of samples
                //

                int modifier = (int)((44100 / (double)reader.WaveFormat.SampleRate));

                List<float> resampled = new List<float>();

                for (int i = 0; i < samples.Count; i += reader.WaveFormat.Channels)
                {
                    for (int z = 0; z < modifier; z++)
                    {
                        for (int x = 0; x < reader.WaveFormat.Channels; x++)
                        {
                            resampled.Add(samples[i + x]);
                        }
                    }
                }

                //
                // Do a Low-Pass filter here
                //

                samples = resampled;
            }

            //
            // Create new track
            //

            WaveTrack track = new WaveTrack (format, samples.ToArray());

            reader.Close ();

            //
            // Get track name
            //

            var fileStream = stream as FileStream;

            if (fileStream != null)
            {
                track.Name = Path.GetFileNameWithoutExtension(fileStream.Name);
            }

            return new List<WaveTrack>
                       {
                           track
                       };
        }

        protected MemoryStream TrackToStream(WaveTrack track)
        {
            MemoryStream stream = new MemoryStream();

            NAudio.Wave.WaveFormat format = NAudio.Wave.WaveFormat.CreateIeeeFloatWaveFormat(track.WaveFormat.SampleRate, track.WaveFormat.Channels);
            WaveFileWriter writer = new WaveFileWriter(stream, format);

            //
            // Create buffer
            //

            float[] buffer = new float[track.Length];

            //
            // Samples are in chunks, so we create buffer from chunks (here, they cannot overlap)
            //

            foreach (WaveChunk chunk in track.TrackChunks)
            {
                chunk.WaveBuffer.CopyTo(buffer, chunk.Offset);
            }

            //
            // Write samples to buffer
            //

            writer.WriteSamples(buffer, 0, buffer.Length);
            writer.Close();
            writer.Dispose();

            return new MemoryStream(stream.ToArray());
        }
    }
}
